package cn.xzzz2020.XJynWeb.utils;

import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

/**
 * 类加载工具类
 * @author xzzz2020
 * @version 1.0
 * @date 2020/7/31 13:20
 */

@Slf4j
public final class ClassUtil {

    private static final String FILE_PROTOCLO = "file";

    private ClassUtil() {
    }

    /**
     * 获取包下类的集合
     *
     * @param packageName 包名
     * @return 获取类的集合
     */
    public static Set<Class<?>> extractPackageClass(String packageName) {
        //1. 获取类的加载器，为了获取项目发布的实际路径
        ClassLoader classLoader = getClassLoader();
        //2. 通过类加载器获取到加载的资源信息
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (url == null) {
            log.warn("unable to retrieve anything from package: " + packageName);
            return null;
        } else {
            log.info("load path is "+url.getProtocol()+"://"+url.getPath());
        }
        //3. 根据不同的资源类型，采用不同的方式获取资源的集合
        Set<Class<?>> classSet = null;
        //过滤出文件类型的资源
        if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCLO)){
            classSet = new HashSet<>();
            File packageDirectory = new File(url.getPath());//获取文件的实际路径
            //递归遍历该目录下所有的class文件
            extractClassFile(classSet,packageDirectory,packageName);
        }
        return classSet;
    }

    /**
     * 递归遍历该目录下所有的class文件
     * @param emptyClassSet 装载目标类的集合
     * @param fileSource 文件或目录
     * @param packageName 加载的包路径
     */
    private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
        if (!fileSource.isDirectory()){

        }else {
            //列出当前目录的所有文件和文件夹
            File[] files = fileSource.listFiles(new FileFilter() {
                /**
                 * 该方法是过滤当前目录下的所有文件和目录
                 * @param file 文件目录
                 * @return 如果是目录，返回true，如果是.class文件，加载到容器中，其他的返回false
                 */
                @Override
                public boolean accept(File file) {
                    if (file.isDirectory()){
                        return true;
                    }else {
                        //获取文件的绝对值路径
                        String absoluteFilePath = file.getAbsolutePath();
                        if (absoluteFilePath.endsWith(".class")){
                            //如果是class文件，直接加载
                            addToClassSet(absoluteFilePath);
                        }
                        //TODO 此处可以加入针对其他类型资源的处理
                    }
                    return false;
                }

                /**
                 * 根据class文件的绝对值路径，获取并生成class对象，并放入classSet中
                 * @param absoluteFilePath 文件的绝对值路径
                 */
                private void addToClassSet(String absoluteFilePath) {
                    //1.从class文件的绝对值路径里提取出包含了package的类名
                    //如/Users/baidu/imooc/springframework/sampleframework/target/classes/cn/xzzz2020/entity/dto/MainPageInfoDTO.class
                    //需要弄成cn.xzzz2020.entity.dto.MainPageInfoDTO
                    absoluteFilePath  = absoluteFilePath.replace(File.separator, ".");
                    String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
                    className = className.substring(0,className.lastIndexOf("."));
                    //2.通过反射机制获取对应的Class对象并加入到classSet里
                    Class<?> targetClass = loadClass(className);
                    emptyClassSet.add(targetClass);
                }
            });
            //递归遍历全部目录
            if (files!=null){//如果file的数组为空，使用foreach，还是会抛出空指针异常
                for (File file : files) {
                    extractClassFile(emptyClassSet,file,packageName);
                }
            }

        }
    }

    /**
     * 获取类加载器
     *
     * @return 当前类加载器
     */
    private static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }


    /**
     * 获取class对象
     * @param className class全限定类名
     * @return
     */
    private static Class<?> loadClass(String className){
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            log.error("Load class error " + e);
            throw new RuntimeException();
        }
    }

    /**
     * 实例化类对象
     * @param clazz 需要实例化的class
     * @param accessible 是否允许使用私有的构造函数
     * @param <T> 对象
     * @return
     */
    public static <T> T newInstance(Class<?> clazz,boolean accessible){
        try {
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(accessible);
            return (T) constructor.newInstance();
        } catch (Exception e) {
            log.error("newInstance "+clazz+" error "+e);
            throw new RuntimeException();
        }
    }

    /**
     * 设置类的属性值
     * @param field 成员变量
     * @param target 类实例
     * @param value 成员变量的值
     * @param accessible 是否允许设置私有属性
     */
    public static void setField(Field field,Object target,Object value,boolean accessible){
        field.setAccessible(accessible);
        try {
            field.set(target,value);
        } catch (Exception e) {
            log.error("setFiled error ",e);
            throw new RuntimeException(e);
        }
    }

}
